<script lang="ts" setup name="upload-oss">
import type { UploadProps, UploadRawFile, UploadRequestOptions, UploadStatus } from 'element-plus'

import { Delete, Document, Plus, Upload, UploadFilled, ZoomIn } from '@element-plus/icons-vue'
import { useGetOssAccessUrl, useGetOssPrivateUrlList } from '@fl/api/anjutong'
import { useMallGetOssAccessUrl } from '@fl/api/supplier'
import { ALLOW_CONTENT_TYPE_DICT } from '@fl/constants'
import { useMessage } from '@fl/hooks/web/use-message'
import { cloneDeep, isEmpty, isString } from 'lodash-es'

// 本文件只允许一张张勾选图片, 不准多选
export type IProps = {
    accept?: string // 允许上传的文件类型, * 代表所有文件类型
    disabled?: boolean
    drag?: boolean // 是否支持拖拽上传
    fileSize?: number // 限制的大小(MB)
    hidePlusIcon?: boolean
    isCheckWidthOrHeight?: boolean
    isMallAdmin?: boolean // 是否为商城运营端, 商城运营端和非运营是两个不同的接口调用
    isPrivate?: boolean // 默认公域
    isVideo?: boolean
    limit?: number // 文件个数
    listType?: 'picture-card' | 'text' // 覆盖默认 list-type, 没有 picture 类型, 加入 video 类型
    showRemoveIcon?: boolean // 显示移除图标不删除图片
    splitSymbol?: string
    tips?: boolean | string // 提示语
}

const props = withDefaults(defineProps<IProps>(), {
    accept: '.jpg,.jpeg,.png,.webp',
    disabled: false,
    drag: false,
    fileSize: 5, // MB
    hidePlusIcon: false,
    isCheckWidthOrHeight: false,
    isPrivate: false,
    isVideo: false,
    limit: 1,
    listType: 'picture-card',
    showRemoveIcon: false,
    splitSymbol: ',',
    tips: false,
})

const emits = defineEmits(['success', 'remove', 'progress'])

const { createErrorModal } = useMessage()
const { mutate: mutateOssAccessUrl } = useGetOssAccessUrl()
const { mutate: mutateMallOssAccessUrl } = useMallGetOssAccessUrl()

const ONE_Megabit = 1024 * 1024

const attrs = useAttrs() as UploadProps

const getBindValue = computed(() => {
    const obj = {
        ...attrs,
        ...props,
    }
    return obj
})

const { accept, fileSize, hidePlusIcon, isCheckWidthOrHeight, isMallAdmin, isPrivate, isVideo, limit, listType, splitSymbol, tips } = toRefs(getBindValue.value)
const isText = computed(() => listType.value === 'text')
const fileTypeText = computed(() => isText.value ? '文件' : isVideo.value ? '视频' : '图片')

// 默认提示, 用户可以覆盖
const finalTips = computed(() => {
    // 如果存在 tips
    if (tips.value) {
        // 进一步判断是否为字符串
        if (isString(tips.value)) {
            return tips.value
        }

        if (accept.value === '*') {
            return `支持任意格式，所选${fileTypeText.value}大小不能超过 ${fileSize.value} MB，最多上传 ${limit.value} 个文件`
        }
        return `支持扩展名: ${accept.value}，所选${fileTypeText.value}大小不能超过 ${fileSize.value} MB，最多上传 ${limit.value} 个文件`
    }

    return false
})

const hiddenAdd = computed(() => {
    // 传普通文件时, 始终显示上传按钮
    if (isText.value) {
        return false
    }

    // 如果为视频, 只能一次传一个
    // if (isVideo.value) {
    //     return true
    // }

    // 上传数量超限
    if (fileList.value && fileList.value.length >= limit.value!) {
        return true
    }

    return false
})

const hidePlusIconFirstTime = computed(() => {
    if (hidePlusIcon.value && fileList.value.length === 0) {
        return true
    }
    else {
        return false
    }
})

// 外部传入的文件 url
// 如果是多个文件, 用逗号分割;
// 私域文件只有后面半截, 需要一个拼接流程
type ISingleFile = { name: string, url: string }
const model = defineModel<ISingleFile[] | string>({ required: true })

const modelIsString = computed(() => {
    return isString(model.value)
})
const initialModelValue = cloneDeep(model.value)
export type UploadFile = {
    name: string
    percentage?: number
    raw?: UploadRawFile
    response?: unknown
    size?: number
    status: UploadStatus
    uid: number
    url?: string
    xhr: any
}
export type UploadFiles = UploadFile[]
// 回显的文件列表
const fileList = ref<UploadFiles>([] as UploadFiles)

// 初始传入的图片回显
onMounted(() => {
    // 类型守卫
    const vStr = initialModelValue as string
    const vArr = initialModelValue as ISingleFile[]
    if (modelIsString.value) {
        const urlList = vStr ? vStr.split(splitSymbol.value) : []
        fileList.value = urlList.map(
            (fullPath: string) => ({
                name: fullPath,
                url: fullPath,
                validUrl: fullPath,
            }),
        ) as any
    }
    else {
        fileList.value = vArr && vArr.map(
            (item: ISingleFile) => ({
                name: item.name,
                url: item.url,
                validUrl: item.url,
            }),
        ) as any
    }
})

watchEffect(() => {
    // 如果传入的值为空, 清掉 fileList, 这在新建表单的取消过程中会十分有用
    if (isEmpty(model.value)) {
        fileList.value = []
    }
})

// 私有化图片只有半截, 回显的图片是定死的, 只在初次生效
const results = useGetOssPrivateUrlList(initialModelValue as string, isPrivate.value)

watchEffect(() => {
    if (results.value.isSuccess) {
        fileList.value = results.value.data.map(
            (fullPath: string) => ({
                name: fullPath,
                url: fullPath,
                validUrl: fullPath,
            }),
        ) as any
    }
})

const dialogImageUrl = ref('')
const dialogVisible = ref(false)
// 判断文件超大
function checkOverSize(paramsSize: number) {
    if (paramsSize / ONE_Megabit > fileSize.value) {
        ElMessage.error(`所选${fileTypeText.value}大小不能超过 ${fileSize.value} MB`)
        return true
    }

    return false
}
const hasUploading = computed(() => {
    if (fileList.value.length === 0)
        return false
    return fileList.value.some(fileItem => fileItem.percentage && fileItem.percentage < 100)
})
defineExpose({ hasUploading })

const contentTypeArr = computed(() => {
    let tmpArr = [] as string[]
    if (accept.value) {
        const acceptArr = accept.value.split(',').map(str => str.toLowerCase())
        tmpArr = acceptArr.map((x: string) => ALLOW_CONTENT_TYPE_DICT[x])
    }

    return tmpArr
})

// 判断文件类型
function checkFileType(fileType: string) {
    console.dir(contentTypeArr, fileType)

    if (!contentTypeArr.value.includes(fileType)) {
        ElMessage.error(`${fileTypeText.value}类型不正确，接收类型为 ${accept.value}`)
        return true
    }

    return false
}

function checkWidthOrHeight(rawFile) {
    return new Promise((resolve, reject) => {
        const img = new Image()
        img.src = URL.createObjectURL(rawFile)
        img.onload = () => {
            if (img.width < 600 && img.height < 600) {
                ElMessage.error(`图片尺寸要求宽度大于600或高度大于600`)
                resolve(true)
            }
            else {
                resolve(false)
            }
        }
        img.onerror = (error) => {
            reject(error)
        }
    })
}

// 文件超出数量限制回调
const handleExceed: UploadProps['onExceed'] = () => {
    ElMessage.error(`最多上传${limit.value}个文件！`)
}

// 上传前的检查
async function handleBeforeUpload(rawFile: UploadRawFile) {
    // 文件限制大小
    if (checkOverSize(rawFile.size!))
        return false

    // 文件限制类型
    if (accept.value === '*') {
        return true
    }
    else {
        if (checkFileType(rawFile.type))
            return false
    }

    // 文件限制宽高
    if (isCheckWidthOrHeight.value) {
        if (await checkWidthOrHeight(rawFile))
            return false
    }

    return true
}

// 上传方法
async function handleHttpRequest(options: UploadRequestOptions) {
    // 直传到数据组
    // uploadTob(options.file)
    // 传到集采商城
    if (import.meta.env.VITE_IS_MALL === 'true') {
        uploadToMall(options.file)
    }
    else {
        // 传到数据组
        uploadTob(options.file)
    }
}

// 上传失败
function handleUploadError(error: Error, uploadFile: UploadRawFile) {
    // 没有上传成功, 把图片给去掉
    createErrorModal({ content: error as unknown as string, title: '上传失败' })
    const uid = uploadFile.uid
    const targetIndex = fileList.value.findIndex(item => item.uid === uid)
    fileList.value.splice(targetIndex, 1)
}
// 直传方法
function sendXhr(file: UploadRawFile, res: any) {
    const xhr = new XMLHttpRequest()
    xhr.open('PUT', res.upload_url, true)
    if (xhr.upload) {
        xhr.upload.onprogress = function (e) {
            const uid = file.uid
            const targetIndex = fileList.value.findIndex(item => item.uid === uid)
            if (targetIndex > -1) {
                fileList.value[targetIndex].percentage = Math.floor(e.loaded / e.total * 100)
                fileList.value[targetIndex].xhr = xhr
            }
        }
    }
    xhr.onload = function () {
        console.log('xhr.status', xhr.status)
        if (xhr.status === 200) {
            handleUploadSuccess(res, file)
        }
        else {
            handleUploadError(new Error('上传失败'), file)
        }
    }

    xhr.onerror = function () {
        handleUploadError(new Error('上传失败'), file)
    }

    xhr.send(file)
}

// 从数据组拿直传地址, 直传华为云
function uploadTob(uploadFile: UploadRawFile) {
    const { name, type } = uploadFile

    mutateOssAccessUrl({
        content_type: type,
        isPrivate: isPrivate.value,
        object_name: name,
    }, {
        onError: (error) => {
            handleUploadError(error, uploadFile)
        },
        onSuccess: (res: any) => {
            sendXhr(uploadFile, res)
        },
    })
}

// 从集采拿直传地址, 直传华为云
function uploadToMall(uploadFile: UploadRawFile) {
    const { name, type } = uploadFile

    mutateMallOssAccessUrl({
        content_type: type,
        isMallAdmin: isMallAdmin.value,
        object_name: name,
    }, {
        onError: (error) => {
            handleUploadError(error, uploadFile)
        },
        onSuccess: (res: any) => {
            sendXhr(uploadFile, res)
        },
    })
}

// 上传成功后的回调
function handleUploadSuccess(res: any, uploadFile: UploadRawFile) {
    // !!只在私域的情况下, url 取 object_key, 其他情况无论是否为集采都取 access_url
    const url = isPrivate.value ? res.object_key : res.access_url

    const uid = uploadFile.uid
    const targetIndex = fileList.value.findIndex(item => item.uid === uid)
    const targetItem = fileList.value.find(item => item.uid === uid)

    // 这个地方不对, 这是一次数组赋值, 但是紧接着取了 currentUrls
    fileList.value.splice(
        targetIndex,
        1,
        {
            ...targetItem,
            percentage: 100,
            validUrl: url,
        } as any,
    )

    nextTick(() => {
        const currentUrls = (fileList.value as any).map(item => item.validUrl)

        if (modelIsString.value) {
            model.value = currentUrls.join(splitSymbol.value)
        }
        else {
            model.value = (fileList.value as any).map(item => ({
                name: item.name,
                url: item.validUrl,
            }))
        }
        emits('success', { file: uploadFile, name: uploadFile.name, url })
    })
}

// 删除文件
function handleRemove(file: UploadFile) {
    const targetIndex = fileList.value.findIndex(item => item.uid === file.uid)

    if (targetIndex >= 0) {
        const currentUrls = fileList.value.map(item => item.url)
        currentUrls.splice(targetIndex, 1)
        if (fileList.value[targetIndex].percentage && fileList.value[targetIndex].percentage < 100) {
            fileList.value[targetIndex].xhr.abort()
        }

        fileList.value.splice(targetIndex, 1)

        if (modelIsString.value) {
            model.value = currentUrls.join(splitSymbol.value)
        }
        else {
            model.value = (fileList.value as any).map(item => ({
                name: item.name,
                url: item.validUrl,
            }))
        }
    }
}
function newFileNameClick(url, name) {
    const xReq = new XMLHttpRequest()
    xReq.open('GET', url, true)
    xReq.responseType = 'blob'
    xReq.onload = function () {
        const url = window.URL.createObjectURL(xReq.response)
        const a = document.createElement('a')
        a.href = url
        a.download = name
        a.click()
        a.remove()
    }
    xReq.send()
}

// 预览文件
function handlePreview(file: UploadFile) {
    if (isText.value) {
        newFileNameClick((file as any).validUrl, file.name)
    }
    else {
        dialogVisible.value = true
        dialogImageUrl.value = file.url!
    }
}
</script>

<template>
    <ElUpload v-bind="getBindValue"
              v-model:file-list="fileList"
              class="w-full"
              :class="{
                  hidden_add: hiddenAdd || disabled,
                  hide_plus_icon: hidePlusIconFirstTime,
                  only_show: disabled,
              }"
              :on-remove="handleRemove"
              :http-request="handleHttpRequest"
              :before-upload="handleBeforeUpload"
              :disabled="disabled"
              :on-exceed="handleExceed"
    >
        <!-- #region 自定义默认内容 -->
        <template v-if="!isVideo"
                  #default
        >
            <ElButton v-if="isText"
                      :icon="Upload"
            >
                上传文件
            </ElButton>

            <ElIcon v-else>
                <Plus />
            </ElIcon>
        </template>

        <template v-if="isVideo">
            <el-icon class="el-icon--upload">
                <UploadFilled />
            </el-icon>

            <div class="upload_hint">
                将文件拖动到此区域
            </div>
        </template>
        <!-- #endregion -->

        <!-- #region 提示说明文字 -->
        <template v-if="!disabled"
                  #tip
        >
            <div v-if="tips"
                 class="el-upload__tip"
            >
                {{ finalTips }}
            </div>
        </template>
        <!-- #endregion -->

        <!-- #region 缩略图模板的内容 -->
        <template #file="{ file }">
            <!-- #region 渲染文件 -->
            <template v-if="isText">
                <div class="flex flex-row flex-nowrap w-full justify-between items-center">
                    <div class="text-align-left flex flex-grow-1 cursor-pointer overflow-hidden items-center"
                         @click.prevent="handlePreview(file)"
                    >
                        <ElIcon>
                            <Document />
                        </ElIcon>

                        <span class="el-upload-list__item-file-name">
                            {{ file.name }}
                        </span>
                    </div>

                    <div v-if="file.percentage < 100"
                         class="fileProgress flex flex-shrink-0 flex-basis-100 relative items-center"
                    >
                        <el-progress :percentage="file.percentage"
                                     status="exception"
                                     :show-text="false"
                        />

                        <div class="absolute left-[100%]">
                            {{ file.percentage }}%
                        </div>
                    </div>

                    <div v-if="!disabled"
                         class="i-material-symbols:delete-outline ml20 text-24 text-red flex-shrink-0 flex-grow-0 cursor-pointer"
                         @click="handleRemove(file)"
                    />
                </div>
            </template>
            <!-- #endregion -->

            <template v-else>
                <video v-if="isVideo"
                       :autoplay="false"
                       name="media"
                       :src="file.url"
                >
                    <source :src="file.url"
                            type="video/mp4"
                    >
                </video>

                <img v-else
                     class="el-upload-list__item-thumbnail"
                     :src="file.url"
                     alt=""
                >

                <div class="el-upload-list__item-actions">
                    <span class="el-upload-list__item-preview"
                          @click="handlePreview(file)"
                    >
                        <ElIcon>
                            <ZoomIn />
                        </ElIcon>
                    </span>

                    <span v-if="!disabled"
                          class="el-upload-list__item-delete"
                          @click="handleRemove(file)"
                    >
                        <ElIcon>
                            <Delete />
                        </ElIcon>
                    </span>

                    <span v-if="showRemoveIcon"
                          class="el-upload-list__item-delete"
                          @click=" emits('remove')"
                    >
                        <ElIcon>
                            <Delete />
                        </ElIcon>
                    </span>
                </div>
            </template>
        </template>
        <!-- #endregion -->
    </ElUpload>

    <slot v-if="(hiddenAdd || disabled) && !fileList.length"
          name="no-file"
    >
        <span>--</span>
    </slot>

    <ElDialog v-model="dialogVisible">
        <div class="flex h-800 justify-center">
            <video v-if="isVideo"
                   class="h-full"
                   autoplay="true"
                   name="media"
            >
                <source :src="dialogImageUrl"
                        type="video/mp4"
                >
            </video>

            <img v-else
                 class="h-full"
                 :src="dialogImageUrl"
            >
        </div>
    </ElDialog>
</template>

<style lang="less" scoped>
:deep(.fileProgress) {
    width: 100px;
    height: 20px;

    .el-progress {
        top: 0 !important;
        position: inherit;
    }
}

.hidden_add {
    :deep(.el-upload, .el-upload--picture-card) {
        display: none;
        background: blue;
    }
}

.hide_plus_icon {
    :deep(.el-icon) {
        display: none;
    }
}

.only_show {
    :deep(.el-upload-list) {
        margin-top: 0;
    }
}

.upload_hint {
    font-size: 12px;
}

:deep(.el-upload-dragger) {
    height: 100%;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
}

// :deep(.el-list-enter-active),
// :deep(.el-list-leave-active) {
//     transition: none;
// }

// :deep(.el-list-enter),
// :deep(.el-list-leave-active) {
//     opacity: 0;
// }
</style>
